package info.u250.c2d.tests.mesh;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ImmediateModeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import info.u250.c2d.engine.Engine;
import info.u250.c2d.engine.EngineDrive;
import info.u250.c2d.engine.Scene;
import info.u250.c2d.engine.resources.AliasResourceManager;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class WaterTest extends Engine {
    @Override
    protected EngineDrive onSetupEngineDrive() {
        return new EngineX();
    }

    @Override
    public void dispose() {
        super.dispose();
    }

    private class EngineX implements EngineDrive {
        @Override
        public void onResourcesRegister(AliasResourceManager<String> reg) {
            reg.texture("W", "data/water.png");
        }

        @Override
        public void dispose() {
        }

        @Override
        public EngineOptions onSetupEngine() {
            final EngineOptions opt = new EngineOptions(new String[]{"data/water.png"}, 800, 480);
            opt.useGL20 = true;
            return opt;
        }

        @Override
        public void onLoadedResourcesCompleted() {
            final Water water = new Water(new TextureRegion(Engine.resource("W", Texture.class)), 200, 240, Color.BLUE, Color.CYAN);
            Engine.setMainScene(new Scene() {
                @Override
                public void render(float delta) {
                    water.render(delta);
                }

                @Override
                public InputProcessor getInputProcessor() {
                    return new InputAdapter() {
                        @Override
                        public boolean touchDown(int screenX, int screenY,
                                                 int pointer, int button) {
                            water.splash(200, 120 + new Random().nextFloat() * 100);
                            return super.touchDown(screenX, screenY, pointer,
                                    button);
                        }
                    };
                }

                @Override
                public void update(float delta) {
                    water.update();
                }

                @Override
                public void hide() {
                }

                @Override
                public void show() {
                }
            });
        }
    }
}

class WaterColumn {
    public float TargetHeight;
    public float Height;
    public float Speed;

    public void Update(float dampening, float tension) {
        float x = TargetHeight - Height;
        Speed += tension * x - Speed * dampening;
        Height += Speed;
    }
}

class Particle {
    public Vector2 Position;
    public Vector2 Velocity;
    public float Orientation;

    public Particle(Vector2 position, Vector2 velocity, float orientation) {
        Position = position;
        Velocity = velocity;
        Orientation = orientation;
    }
}

class Water {
    public Water(TextureRegion particleTexture, int segment, float height, Color topColor, Color endColor) {
        this.particleTexture = particleTexture;
        columns = new WaterColumn[segment];

        for (int i = 0; i < columns.length; i++) {
            columns[i] = new WaterColumn();
            columns[i].Height = height;
            columns[i].TargetHeight = height;
            columns[i].Speed = 0;

        }
        scale = Engine.getWidth() / (columns.length - 1);
        this.color1 = endColor;
        this.color2 = topColor;

        fbo = new FrameBuffer(Pixmap.Format.RGBA8888, (int) Engine.getWidth(), (int) Engine.getHeight(), false);
    }

    Color color1 = new Color(0.2f, 0.7f, 1f, 0.9f);
    Color color2 = new Color(0.2f, 0.5f, 1f, 0.8f);
    public float tension = 0.025f;
    public float dampening = 0.025f;
    public float spread = 0.25f;
    int tinkness = 2;
    TextureRegion particleTexture;
    float scale = 0;
    Random rnd = new Random();
    WaterColumn[] columns = null;
    List<Particle> particles = new ArrayList<Particle>();
    FrameBuffer fbo;

    public void update() {
        for (int i = 0; i < columns.length; i++) columns[i].Update(dampening, tension);

        float[] lDeltas = new float[columns.length];
        float[] rDeltas = new float[columns.length];

        // do some passes where columns pull on their neighbours
        for (int j = 0; j < 8; j++) {
            for (int i = 0; i < columns.length; i++) {
                if (i > 0) {
                    lDeltas[i] = spread * (columns[i].Height - columns[i - 1].Height);
                    columns[i - 1].Speed += lDeltas[i];
                }
                if (i < columns.length - 1) {
                    rDeltas[i] = spread * (columns[i].Height - columns[i + 1].Height);
                    columns[i + 1].Speed += rDeltas[i];
                }
            }

            for (int i = 0; i < columns.length; i++) {
                if (i > 0)
                    columns[i - 1].Height += lDeltas[i];
                if (i < columns.length - 1)
                    columns[i + 1].Height += rDeltas[i];
            }
        }

        for (Particle particle : particles) {
            updateParticle(particle);
        }
    }

    public void render(float delta) {
        Gdx.gl.glEnable(GL20.GL_BLEND);
        final ImmediateModeRenderer renderer = Engine.getShapeRenderer().getRenderer();
        Engine.getShapeRenderer().begin(ShapeType.Filled);
        for (int i = 1; i < columns.length; i++) {

            Vector2 p1 = new Vector2((i - 1) * scale, 0);
            Vector2 p2 = new Vector2((i - 1) * scale, columns[i - 1].Height);
            Vector2 p3 = new Vector2(i * scale, columns[i].Height);
            Vector2 p4 = new Vector2(i * scale, 0);

            renderer.color(color1.r, color1.g, color1.b, color1.a);
            renderer.vertex(p1.x, p1.y, 0);
            renderer.color(color2.r, color2.g, color2.b, color2.a);
            renderer.vertex(p2.x, p2.y, 0);
            renderer.color(color2.r, color2.g, color2.b, color2.a);
            renderer.vertex(p3.x, p3.y, 0);

            renderer.color(color2.r, color2.g, color2.b, color2.a);
            renderer.vertex(p3.x, p3.y, 0);
            renderer.color(color1.r, color1.g, color1.b, color1.a);
            renderer.vertex(p4.x, p4.y, 0);
            renderer.color(color1.r, color1.g, color1.b, color1.a);
            renderer.vertex(p1.x, p1.y, 0);


            renderer.color(color1.r, color1.g, color1.b, color1.a * 0.3f);
            renderer.vertex(p1.x, p2.y - tinkness, 0);
            renderer.color(color2.r, color2.g, color2.b, color2.a * 0.3f);
            renderer.vertex(p2.x, p2.y, 0);
            renderer.color(color2.r, color2.g, color2.b, color2.a * 0.3f);
            renderer.vertex(p3.x, p3.y, 0);

            renderer.color(color2.r, color2.g, color2.b, color2.a * 0.3f);
            renderer.vertex(p3.x, p3.y, 0);
            renderer.color(color1.r, color1.g, color1.b, color1.a * 0.3f);
            renderer.vertex(p4.x, p3.y - tinkness, 0);
            renderer.color(color1.r, color1.g, color1.b, color1.a * 0.3f);
            renderer.vertex(p1.x, p2.y - tinkness, 0);
        }
        Engine.getShapeRenderer().end();


        fbo.begin();
        Gdx.graphics.getGL20().glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
        Engine.getSpriteBatch().setColor(color2);
        Engine.getSpriteBatch().setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
        Engine.getSpriteBatch().begin();
        for (Particle p : particles) {
            Engine.getSpriteBatch().draw(particleTexture, p.Position.x, p.Position.y,
                    0, 0, particleTexture.getRegionWidth(), particleTexture.getRegionHeight(),
                    1, 1, p.Orientation);
        }
        Engine.getSpriteBatch().end();
        Engine.getSpriteBatch().setShader(null);
        fbo.end();

        Engine.getSpriteBatch().setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
        Engine.getSpriteBatch().begin();
        Engine.getSpriteBatch().draw(fbo.getColorBufferTexture(), 0, 0, fbo.getColorBufferTexture().getWidth(), fbo.getColorBufferTexture().getHeight(), 0, 0, 1, 1);
        Engine.getSpriteBatch().end();
        Engine.getSpriteBatch().setColor(Color.WHITE);
        Engine.getSpriteBatch().setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
    }

    void updateParticle(Particle particle) {
        float Gravity = -0.3f;
        particle.Velocity.y += Gravity;
        particle.Position.add(particle.Velocity);
        particle.Orientation = GetAngle(particle.Velocity);
    }

    private float GetAngle(Vector2 vector) {
        return (float) Math.atan2(vector.y, vector.x);
    }

    public float GetHeight(float x) {
        if (x < 0 || x > 800)
            return 240;

        return columns[(int) (x / scale)].Height;
    }

    private void CreateParticle(Vector2 pos, Vector2 velocity) {
        particles.add(new Particle(pos, velocity, 0));
    }

    private Vector2 FromPolar(float angle, float magnitude) {
        return new Vector2((float) Math.cos(angle), (float) Math.sin(angle))
                .scl(magnitude);
    }

    private float GetRandomFloat(float min, float max) {
        return (float) rnd.nextDouble() * (max - min) + min;
    }

    private Vector2 GetRandomVector2(float maxLength) {
        return FromPolar(GetRandomFloat(-MathUtils.PI, -MathUtils.PI),
                GetRandomFloat(0, maxLength));
    }

    private void CreateSplashParticles(float xPosition, float speed) {
        float y = GetHeight(xPosition);

        if (speed > 120) {
            for (int i = 0; i < speed / 8; i++) {
                Vector2 pos = new Vector2(xPosition, y)
                        .add(GetRandomVector2(40));
                Vector2 vel = FromPolar(GetRandomFloat(-150, -30)
                                * MathUtils.degreesToRadians,
                        GetRandomFloat(0, 0.5f * (float) Math.sqrt(speed)));
                vel.y *= -1;
                CreateParticle(pos, vel);
            }
        }
    }

    public void splash(float xPosition, float speed) {
        int index = (int) MathUtils.clamp(xPosition / scale, 0,
                columns.length - 1);
        for (int i = Math.max(0, index - 0); i < Math.min(columns.length - 1,
                index + 1); i++) {
            columns[index].Speed = speed;
        }
        CreateSplashParticles(xPosition, speed);

    }

}
